import logging
import os
from datetime import timedelta

from aiogram import Bot, types
from aiogram_dialog import DialogManager, ShowMode
from arq import ArqRedis
from arq.jobs import Job
from fluentogram import TranslatorRunner
from urlextract import URLExtract

from dc_shop.config import Settings
from dc_shop.core.constants import INVOICES_FOLDER
from dc_shop.core.exceptions.order import ProductOutOfStock
from dc_shop.core.models.enums.currency import Currency
from dc_shop.core.models.enums.order import DeliveryMethod, OrderStatus
from dc_shop.core.models.enums.user import UserRole
from dc_shop.core.utils.date_time import get_current_timestamp
from dc_shop.core.utils.files import get_file_extension
from dc_shop.core.utils.order import calculate_total_summ, generate_product_lines
from dc_shop.database.models import OrderLine
from dc_shop.database.services.discount import DiscountService
from dc_shop.database.services.order import OrderService
from dc_shop.database.services.payment_method import PaymentMethodService
from dc_shop.database.services.product import ProductService
from dc_shop.database.services.promocode import PromocodeService
from dc_shop.database.services.shop import ShopService
from dc_shop.database.services.statistics import StatsService
from dc_shop.database.services.user import UserService
from dc_shop.tg_bot.keyboards.inline import go_to_shopping_kb, order_payment_confirmation_kb, order_shipping_kb
from dc_shop.tg_bot.services.tg_helpers import answer_msg_with_autodelete, broadcast, delete_msg, send_tg_message

logger = logging.getLogger(__name__)


async def set_self_delivery(_, __, manager: DialogManager):
    manager.dialog_data["is_self_delivery"] = True
    await manager.next()


async def set_user_delivery_address(message: types.Message, _, manager: DialogManager, address: str):
    """Обновляем адрес пользователя"""

    locale: TranslatorRunner = manager.middleware_data.get("locale")
    # Достаем ссылки из сообщения юзера
    address = address[0].lower() + address[1:]
    urls = URLExtract().find_urls(address)

    if len(urls) == 0 or "maps" not in urls[0]:
        await answer_msg_with_autodelete(message, locale.get("wrong-address-msg"))
        return

    user_service: UserService = manager.middleware_data.get("user_service")
    await user_service.update_user_address(message.from_user.id, urls[0])

    manager.show_mode = ShowMode.EDIT

    await message.delete()


async def set_user_contact_phone(message: types.Message, _, manager: DialogManager, phone: str):
    """Обновляем телефон пользователя"""

    user_service: UserService = manager.middleware_data.get("user_service")
    await user_service.update_user_phone(message.from_user.id, phone)

    manager.show_mode = ShowMode.EDIT

    await message.delete()


async def set_user_contact_name(message: types.Message, _, manager: DialogManager, name: str):
    """Обновляем имя пользователя"""

    user_service: UserService = manager.middleware_data.get("user_service")
    await user_service.update_user_name(message.from_user.id, name)

    manager.show_mode = ShowMode.EDIT

    await message.delete()


async def create_or_update_order(call: types.CallbackQuery, __, manager: DialogManager, payment_method_id: str):
    """Создаем заказ из корзины пользователя / обновляем существующий заказ"""

    bot: Bot = manager.middleware_data.get("bot")
    locale: TranslatorRunner = manager.middleware_data.get("locale")
    config: Settings = manager.middleware_data.get("config")
    order_service: OrderService = manager.middleware_data.get("order_service")
    user_service: UserService = manager.middleware_data.get("user_service")
    product_service: ProductService = manager.middleware_data.get("product_service")
    shop_service: ShopService = manager.middleware_data.get("shop_service")
    payment_method_service: PaymentMethodService = manager.middleware_data.get("payment_method_service")
    stats_service: StatsService = manager.middleware_data.get("stats_service")
    discount_service: DiscountService = manager.middleware_data.get("discount_service")
    promocode_service: PromocodeService = manager.middleware_data.get("promocode_service")
    arq_redis: ArqRedis = manager.middleware_data.get("arqredis")

    manager.dialog_data["payment_method_id"] = payment_method_id

    payment_method = await payment_method_service.get_payment_method_by_id(int(payment_method_id))

    user = await user_service.get_user_by_telegram_id(call.from_user.id)
    shop = await shop_service.get_shop_by_delivery_zone_id(user.delivery_zone_id)

    promocode_id: int = manager.dialog_data.get("promocode_id")

    promocode_value = None
    if promocode_id:
        promocode = await promocode_service.get_promocode_by_id(promocode_id)
        promocode_value = promocode.amount

    if manager.start_data and manager.start_data.get("order_id"):
        order_id: int = manager.start_data.get("order_id")
    else:
        order_id: int = manager.dialog_data.get("order_id")

    # Обновляем сумму и валюту заказа, если он уже создан
    if order_id:
        order = await order_service.get_order_by_id(order_id)
        order_summ = await calculate_total_summ(
            items=order.order_lines,
            shop_id=order.shop_id,
            payment_method=payment_method,
            product_service=product_service,
            discount_service=discount_service,
            promocode_value=promocode_value,
        )
        await order_service.update_order_summ(order_id, order_summ)
        await order_service.update_order_payment_method(order_id, int(payment_method_id))
    else:
        cart = await user_service.get_user_cart(call.from_user.id)

        order_summ = await calculate_total_summ(
            items=cart.products,
            shop_id=shop.id,
            payment_method=payment_method,
            product_service=product_service,
            discount_service=discount_service,
            promocode_value=promocode_value,
        )
        order_lines = [OrderLine(product_id=item.product_id, quantity=item.quantity) for item in cart.products]

        delivery_method = DeliveryMethod.courier
        if manager.dialog_data.get("is_self_delivery"):
            delivery_method = DeliveryMethod.self_pickup

        order_status = None
        if payment_method.currency == Currency.cash:
            order_status = OrderStatus.payment_confirmed
        try:
            order = await order_service.create_order(
                summ=order_summ,
                order_status=order_status,
                user_tg_id=call.from_user.id,
                shop_id=shop.id,
                payment_method_id=int(payment_method_id),
                order_lines=order_lines,
                promocode_id=promocode_id,
                delivery_method=delivery_method,
            )
        except ProductOutOfStock:
            await call.message.answer(
                locale.get("product-out-of-stock-msg"),
                reply_markup=go_to_shopping_kb(locale),
            )
            return

        order_id = order.id
        await stats_service.add_stats(products_purchased=len(order_lines))
        await stats_service.add_stats(orders_created=1)
        for line in order_lines:
            await product_service.increase_product_sold_count(line.product_id, line.quantity)

        await send_tg_message(
            bot,
            config.tg_bot.admin_channel_id,
            locale.get(
                "new-order-msg",
                order_id=order_id,
                shop_id=shop.id,
                order_summ=order.summ,
                currency_symbol=payment_method.currency_symbol.value,
            ),
        )

    # Генерируем текст с калькуляцией товаров
    order_product_lines_text = await generate_product_lines(
        items=order.order_lines,
        shop_id=shop.id,
        payment_method=payment_method,
        product_service=product_service,
        discount_service=discount_service,
        promocode_value=promocode_value,
    )
    manager.dialog_data["order_id"] = order_id
    order = await order_service.get_order_by_id(order_id)
    # Записываем новую сумму заказа в dialog_data
    manager.dialog_data["order_summ"] = order.summ
    manager.dialog_data["order_product_lines_text"] = order_product_lines_text

    if payment_method.currency == Currency.cash:
        admins = await user_service.get_users_by_role_and_shop_id(UserRole.admin, order.shop_id)
        stuff = await user_service.get_users_by_role_and_shop_id(UserRole.stuff, order.shop_id)
        await broadcast(
            bot,
            admins,
            locale.get("new-order-for-payment-confirmation-msg", order_id=order_id),
            reply_markup=order_payment_confirmation_kb(locale, order_id=order_id),
        )
        delivery_type = "DELIVERY"
        if order.delivery_method == DeliveryMethod.self_pickup:
            delivery_type = "PICKUP BY CUSTOMER"
        await broadcast(
            bot,
            stuff,
            locale.get("new-order-for-shipping-msg", delivery_type=delivery_type, order_id=order_id),
            reply_markup=order_shipping_kb(locale, order_id),
        )
        await send_tg_message(
            bot,
            config.tg_bot.admin_channel_id,
            locale.get(
                "order-status-changed-msg",
                order_id=order_id,
                status=OrderStatus.payment_not_confirmed.value,
            ),
        )
        # Отправляем сообщение о сборке заказа покупателю
        user_message = locale.get("order-collecting-msg")
        if order.delivery_method == DeliveryMethod.self_pickup:
            user_message = locale.get("order-collecting-self-pickup-msg", shop_address=shop.contact_address)
        await call.message.answer(user_message)

        await manager.reset_stack()
    else:
        notify_expiring_payment_job = await arq_redis.enqueue_job(
            "notify_expiring_payment",
            user_tg_id=call.from_user.id,
            order_id=order_id,
            _defer_by=timedelta(minutes=50),
        )
        notify_expired_payment_job = await arq_redis.enqueue_job(
            "notify_expired_payment",
            user_tg_id=call.from_user.id,
            order_id=order_id,
            _defer_by=timedelta(hours=1),
        )
        manager.dialog_data["notify_expiring_payment_job_id"] = notify_expiring_payment_job.job_id
        manager.dialog_data["notify_expired_payment_job_id"] = notify_expired_payment_job.job_id

        await manager.next()


async def go_back_with_job_cancel(_, __, manager: DialogManager):
    """
    Переходим назад к выбору способа оплаты
    Отменяем текущие задачи на уведомление пользователя
    """

    arq_redis: ArqRedis = manager.middleware_data.get("arqredis")

    await Job(manager.dialog_data["notify_expiring_payment_job_id"], redis=arq_redis).abort()
    await Job(manager.dialog_data["notify_expired_payment_job_id"], redis=arq_redis).abort()

    await manager.back()


async def process_invoice_screenshot(message: types.Message, _, manager: DialogManager):
    """Валидируем и сохраняем скриншот об оплате заказа"""

    bot: Bot = manager.middleware_data.get("bot")
    locale: TranslatorRunner = manager.middleware_data.get("locale")
    config: Settings = manager.middleware_data.get("config")
    order_service: OrderService = manager.middleware_data.get("order_service")
    user_service: UserService = manager.middleware_data.get("user_service")

    order_id: int = manager.dialog_data.get("order_id")

    if not message.document and not message.photo:
        await answer_msg_with_autodelete(message, locale.get("wrong-input-msg"))
        return

    if message.document:
        file = message.document
        extension = get_file_extension(file.file_name)
        if extension not in ["jpeg", "jpg", "png"]:
            await answer_msg_with_autodelete(message, locale.get("wrong-photo-extension"))
            return

    elif message.photo:
        file = message.photo[-1]

    file = await bot.get_file(file.file_id)
    msg = await message.answer(locale.get("photo-processing-msg"))

    filename = f"{message.from_user.id}_{get_current_timestamp()}.png"

    destination_path = os.path.join(INVOICES_FOLDER, filename)
    await bot.download_file(file.file_path, destination_path)

    await order_service.update_order_invoice_path(order_id, filename)
    await delete_msg(msg)

    await message.answer(locale.get("after-payment-msg"))

    order = await order_service.get_order_by_id(order_id)

    # Отправляем сообщение для подтверждения оплаты админам магазина
    admins = await user_service.get_users_by_role_and_shop_id(UserRole.admin, order.shop_id)
    await broadcast(
        bot,
        admins,
        locale.get("new-order-for-payment-confirmation-msg", order_id=order_id),
        reply_markup=order_payment_confirmation_kb(locale, order_id=order_id),
    )

    await send_tg_message(
        bot,
        config.tg_bot.admin_channel_id,
        locale.get(
            "order-status-changed-msg",
            order_id=order_id,
            status=OrderStatus.payment_not_confirmed.value,
        ),
    )

    await manager.reset_stack()
